////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include "Test_PathfindingGame.hpp"

#include <sstream>

#include <SFML/Graphics.hpp>

#include <SmegAI/Movement/Pathfinding/Algorithms/AStar.hpp>
#include <SmegAI/Movement/Pathfinding/Algorithms/JumpPointSearch.hpp>

////////////////////////////////////////////////////////////
/// Defines
////////////////////////////////////////////////////////////
#define CONST_SIZE              10
#define ENABLE_STEP_SIZE_ADD    4
#define DISABLE_STEP_SIZE_ADD   5

////////////////////////////////////////////////////////////
/// Constructor
////////////////////////////////////////////////////////////
Test_PathfindingGame::Test_PathfindingGame() :
    GameBase(),
    m_HelpString("Help :\n" \
                " - F1 : Display/Hide this help\n" \
                " - F2 : Remove obstacles\n" \
                " - F3 : Clear Pathfinding\n" \
                " - F4 : ALL Pathfindings\n" \
                " - F5 : AStar\n" \
                " - F6 : JPS\n" \
                " - F9 : Toggle Step by Step [%s]\n" \
                " - F10 : Step\n" \
                " - Click : Toggle obstacles\n" \
                " - Drag Begin/End points\n"),
    m_FinalHelpString(),
    m_pFont( NULL ),
    m_pHelp( NULL ),
    m_pPathfindingInfo( NULL ),
    m_bShowHelp( true ),
    m_bDragBegin( false ),
    m_bDragEnd( false ),
    m_bDrawObstacles( false ),
    m_bClearWall( false ),
    m_pBeginShape( NULL ),
    m_pEndShape( NULL ),
    m_pAIGrid( NULL ),
    m_bStepByStep( false ),
    m_bDoStep( false ),
    m_PathfindingType( eType_None ),
    m_pCurrentAlgorithm( NULL ),
    m_pAStar( NULL ),
    m_pJumpPoint( NULL )
{
}

////////////////////////////////////////////////////////////
/// Destructor
////////////////////////////////////////////////////////////
Test_PathfindingGame::~Test_PathfindingGame()
{
}

////////////////////////////////////////////////////////////
/// Init the game
////////////////////////////////////////////////////////////
void Test_PathfindingGame::Init( sf::VideoMode _Mode, const char* _pTitle, sf::Uint32 _Style, const sf::ContextSettings& _Settings ) {
    GameBase::Init( _Mode, _pTitle, _Style, _Settings );
    
    // // Help
    m_pFont = new sf::Font();
    if ( m_pFont->loadFromFile( "data/fonts/COUR.ttf" ) ) {
        m_pHelp = new sf::Text(m_FinalHelpString,
                        *m_pFont,
                        12);
        m_pHelp->setColor( sf::Color::White );

        m_pPathfindingInfo = new sf::Text("",
                        *m_pFont,
                        12);
        m_pPathfindingInfo->setColor( sf::Color::White );
        m_pPathfindingInfo->setPosition( _Mode.width - 200.0f, 0.0f );
    }

    // // Misc
    // Begin point
    m_BeginPoint = sf::Vector2f( CONST_SIZE * 4.0f, CONST_SIZE * 7.0f );
    m_pBeginShape = new sf::RectangleShape( sf::Vector2f( CONST_SIZE, CONST_SIZE ) );
    m_pBeginShape->setPosition( m_BeginPoint );
    m_pBeginShape->setFillColor( sf::Color::Blue );

    // End Point
    m_EndPoint = sf::Vector2f( CONST_SIZE * 12.0f, CONST_SIZE * 7.0f );
    m_pEndShape = new sf::RectangleShape( sf::Vector2f( CONST_SIZE, CONST_SIZE ) );
    m_pEndShape->setPosition( m_EndPoint );
    m_pEndShape->setFillColor( sf::Color::Red );
    
    // // Pathfinding
    m_pAIGrid = new smeg::ai::Grid( _Mode.width, _Mode.height, CONST_SIZE );

    m_pAStar = new smeg::ai::AStar( m_pAIGrid );
    m_pJumpPoint = new smeg::ai::JumpPointSearch( m_pAIGrid );
}

////////////////////////////////////////////////////////////
/// DeInit the game
////////////////////////////////////////////////////////////
void Test_PathfindingGame::DeInit() {
    delete m_pFont;
    m_pFont = NULL;
    delete m_pHelp;
    m_pHelp = NULL;
    delete m_pPathfindingInfo;
    m_pPathfindingInfo = NULL;
    
    delete m_pBeginShape;
    m_pBeginShape = NULL;
    delete m_pEndShape;
    m_pEndShape = NULL;
    
    delete m_pAIGrid;
    m_pAIGrid = NULL;
    m_pCurrentAlgorithm = NULL;

    delete m_pAStar;
    m_pAStar = NULL;
    delete m_pJumpPoint;
    m_pJumpPoint = NULL;

    GameBase::DeInit();
}

////////////////////////////////////////////////////////////
/// Manage an incoming event
////////////////////////////////////////////////////////////
void Test_PathfindingGame::ManageEvent( const sf::Event& _Event ) {
    GameBase::ManageEvent( _Event );

    switch ( _Event.type ) {
    case sf::Event::KeyPressed:
        switch (_Event.key.code) {
        case sf::Keyboard::F1:
            m_bShowHelp = !m_bShowHelp;
            break;
        case sf::Keyboard::F2:
            m_pAIGrid->RemoveAllObstacles();
            ClearAllPathfinding();
            break;
        case sf::Keyboard::F3:
            ClearAllPathfinding();
            break;
        case sf::Keyboard::F4:
            ClearAllPathfinding();

            // AStar
            FindPath( m_pAStar, m_AStarTime );

            // JPS
            FindPath( m_pJumpPoint, m_JPSTime );

            m_PathfindingType = eType_All;
            break;
        case sf::Keyboard::F5:
            ClearAllPathfinding();
            
            FindPath( m_pAStar, m_AStarTime );

            m_PathfindingType = eType_AStar;
            m_pCurrentAlgorithm = m_pAStar;
            break;
        case sf::Keyboard::F6:
            ClearAllPathfinding();
            
            FindPath( m_pJumpPoint, m_JPSTime );

            m_PathfindingType = eType_JPS;
            m_pCurrentAlgorithm = m_pJumpPoint;
            break;

        case sf::Keyboard::F9:
            ClearAllPathfinding();
            m_bStepByStep = !m_bStepByStep;
            m_bDoStep = true;
            break;

        case sf::Keyboard::F10:
            m_bDoStep = true;
            break;
        }
        break;

    case sf::Event::MouseButtonPressed:
        if ( _Event.mouseButton.x - _Event.mouseButton.x % CONST_SIZE == m_BeginPoint.x &&
            _Event.mouseButton.y - _Event.mouseButton.y % CONST_SIZE == m_BeginPoint.y ) {
            m_bDragBegin = true;
        } else if ( _Event.mouseButton.x - _Event.mouseButton.x % CONST_SIZE == m_EndPoint.x &&
            _Event.mouseButton.y - _Event.mouseButton.y % CONST_SIZE == m_EndPoint.y ) {
            m_bDragEnd = true;
        } else if ( sf::Keyboard::isKeyPressed( sf::Keyboard::LControl ) ) {
            if ( sf::Mouse::Left == _Event.mouseButton.button ) {
                m_BeginPoint.x = (float)_Event.mouseButton.x - _Event.mouseButton.x % CONST_SIZE;
                m_BeginPoint.y = (float)_Event.mouseButton.y - _Event.mouseButton.y % CONST_SIZE;

                m_pBeginShape->setPosition( m_BeginPoint );
            } else {
                m_EndPoint.x = (float)_Event.mouseButton.x - _Event.mouseButton.x % CONST_SIZE;
                m_EndPoint.y = (float)_Event.mouseButton.y - _Event.mouseButton.y % CONST_SIZE;

                m_pEndShape->setPosition( m_EndPoint );
            }
        } else {
            m_bClearWall = m_pAIGrid->IsBlocked( _Event.mouseButton.x, _Event.mouseButton.y, true );
            m_pAIGrid->ToggleObstacle( _Event.mouseButton.x, _Event.mouseButton.y, true );
            m_bDrawObstacles = true;
        }
        ClearAllPathfinding();
        break;

    case sf::Event::MouseButtonReleased:
        m_bDragBegin = false;
        m_bDragEnd = false;
        m_bDrawObstacles = false;
        break;

    case sf::Event::MouseMoved:
        if ( m_bDragBegin ) {
            m_BeginPoint.x = (float)_Event.mouseMove.x - _Event.mouseMove.x % CONST_SIZE;
            m_BeginPoint.y = (float)_Event.mouseMove.y - _Event.mouseMove.y % CONST_SIZE;

            m_pBeginShape->setPosition( m_BeginPoint );
        } else if ( m_bDragEnd ) {
            m_EndPoint.x = (float)_Event.mouseMove.x - _Event.mouseMove.x % CONST_SIZE;
            m_EndPoint.y = (float)_Event.mouseMove.y - _Event.mouseMove.y % CONST_SIZE;

            m_pEndShape->setPosition( m_EndPoint );
        } else if ( m_bDrawObstacles ) {
            if ( m_bClearWall ) {
                m_pAIGrid->MarkAsUnblocked( _Event.mouseMove.x, _Event.mouseMove.y, true );
            } else {
                m_pAIGrid->MarkAsBlocked( _Event.mouseMove.x, _Event.mouseMove.y, true );
            }
        }
        break;
    }
}

////////////////////////////////////////////////////////////
/// Call all the Update in the other systems
////////////////////////////////////////////////////////////
void Test_PathfindingGame::Update() {
    GameBase::Update();

    if ( m_bStepByStep && m_bDoStep ) {
        if ( m_PathfindingType & eType_AStar ) {
            if ( m_pAStar->DebugStepPath( m_BeginPoint, m_EndPoint ) ) {
                m_PathfindingType = ePathfindingType(m_PathfindingType ^ eType_AStar);
            }
        }
        if ( m_PathfindingType & eType_JPS ) {
            if ( m_pJumpPoint->DebugStepPath( m_BeginPoint, m_EndPoint ) ) {
                m_PathfindingType = ePathfindingType(m_PathfindingType ^ eType_JPS);
            }
        }
    }
    m_bDoStep = false;
}

////////////////////////////////////////////////////////////
/// Call all the Render in the other systems
////////////////////////////////////////////////////////////
void Test_PathfindingGame::Render() {
    m_pAIGrid->DebugRender( m_pRenderWindow, true );

    m_pRenderWindow->draw( *m_pBeginShape );
    m_pRenderWindow->draw( *m_pEndShape );

    if ( m_pCurrentAlgorithm ) {
        m_pCurrentAlgorithm->DebugRenderFindPath( m_pRenderWindow );
    }

    if ( sf::Time::Zero != m_AStarTime || sf::Time::Zero != m_JPSTime ) {
        std::stringstream infoStr;
        infoStr << "Pathfinding Info :" << std::endl;
        if ( sf::Time::Zero != m_AStarTime ) {
            infoStr << "- AStar : " << m_AStarTime.asSeconds() << "s" << std::endl;
        }
        if ( sf::Time::Zero != m_JPSTime ) {
            infoStr << "- JPS : " << m_JPSTime.asSeconds() << "s" << std::endl;
        }
        if ( sf::Time::Zero != m_AStarTime && sf::Time::Zero != m_JPSTime ) {
            if ( m_AStarTime < m_JPSTime ) {
                infoStr << "- JPS = " << ( m_JPSTime.asSeconds() / m_AStarTime.asSeconds() ) << "* AStar" << std::endl;
            } else if ( m_AStarTime > m_JPSTime ) {
                infoStr << "- AStar = " << ( m_AStarTime.asSeconds() / m_JPSTime.asSeconds() ) << "* JPS" << std::endl;
            } else {
                infoStr << "- AStar == JPS" << std::endl;
            }
        }

        m_pPathfindingInfo->setString( infoStr.str() );
        m_pRenderWindow->draw( *m_pPathfindingInfo );
    }
    DisplayHelp();
}

////////////////////////////////////////////////////////////
/// Show/Hide the help
////////////////////////////////////////////////////////////
void Test_PathfindingGame::DisplayHelp() {
    if ( m_bShowHelp ) {
        if ( m_bStepByStep ) {
            m_FinalHelpString.resize( m_HelpString.size() + ENABLE_STEP_SIZE_ADD, 0 );
            sprintf( (char *)m_FinalHelpString.c_str(), m_HelpString.c_str(), "enabled" );
        } else {
            m_FinalHelpString.resize( m_HelpString.size() + DISABLE_STEP_SIZE_ADD, 0 );
            sprintf( (char *)m_FinalHelpString.c_str(), m_HelpString.c_str(), "disabled" );
        }
        m_pHelp->setString( m_FinalHelpString );
        m_pRenderWindow->draw( *m_pHelp );
    }
}

////////////////////////////////////////////////////////////
/// Find a path with the given algorithm
////////////////////////////////////////////////////////////
void Test_PathfindingGame::FindPath( smeg::ai::BasePathfindingAlgorithm* _pAlgorithm, sf::Time& _AlgorithmTime ) {
    if ( m_bStepByStep ) {
        _pAlgorithm->DebugStartPath( m_BeginPoint, m_EndPoint );
    } else {
        m_PathfindingClock.restart();
        _pAlgorithm->FindPath( m_BeginPoint, m_EndPoint );
        _AlgorithmTime = m_PathfindingClock.getElapsedTime();
    }
}

////////////////////////////////////////////////////////////
/// Clear all the pathfinding algorithms
////////////////////////////////////////////////////////////
void Test_PathfindingGame::ClearAllPathfinding() {
    m_pAStar->ClearPath();
    m_pJumpPoint->ClearPath();
    
    m_AStarTime = sf::Time::Zero;
    m_JPSTime   = sf::Time::Zero;

    m_PathfindingType = eType_None;
    m_pCurrentAlgorithm = NULL;
}